package de.sendorian.app.forumArchive;


import static de.sendorian.app.forumArchive.renamer.Renamers.*;
import static org.apache.commons.lang.StringUtils.*;
import static org.hamcrest.Matchers.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.hamcrest.Matcher;

import de.sendorian.app.forumArchive.domain.Archive;
import de.sendorian.app.forumArchive.domain.File;
import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Post;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.renamer.FileRenamer;
import de.sendorian.util.io.FileWriter;

public class Downloader {

	private static Logger logger = Logger.getLogger(Downloader.class.getName());

	FileRenamer renamer = nothing();

	Matcher<Post> postMatcher = anything();

	Matcher<Topic> topicMatcher = anything();


	public Downloader(FileRenamer renamer) {
		this.renamer = renamer;
	}


	public Downloader(Matcher<Post> postMatcher, FileRenamer renamer) {
		this(renamer);
		this.postMatcher = postMatcher;
	}

	public Downloader(Matcher<Post> postMatcher, Matcher<Topic> topicMatcher, FileRenamer renamer) {
		this(renamer);
		this.postMatcher = postMatcher;
		this.topicMatcher = topicMatcher;
	}


	public void downloadForums(List<Forum> forums) throws IOException {
		for (Forum forum : forums) {
			downloadForum(forum);
		}
	}


	public void downloadForum(Forum forum) throws IOException {
		String downloadedFilesInfo = "";
		Set<String> externalLinks = new TreeSet<String>();
		for (Topic topic : forum.getTopics()) {
			if (topicMatcher.matches(topic)) {
				TopicInfos topicInfos = downloadPosts(topic, forum.getName());
				downloadedFilesInfo += topicInfos.getDownloadedFilesString();
				externalLinks.addAll(topicInfos.getExternalLinks());
			}
		}

		String forumDir = Config.get().getDownloadDir() + forum.getName();
		String dateString = DateFormatUtils.format(new Date(), "yyyy_MM_dd hh:mm");
		if (!isEmpty(downloadedFilesInfo)) {
			saveTextFile(downloadedFilesInfo, forumDir, "downloaded files " + dateString + ".txt");
		}

		String externalLinksString = getExternalLinksString(externalLinks);
		if (!isEmpty(externalLinksString)) {
			saveTextFile(externalLinksString, forumDir, "external links " + dateString + ".txt");
		}
	}


	public TopicInfos downloadPosts(Topic topic, String forumName) {
		String forumPath = Config.get().getDownloadDir() + forumName;
		String path = forumPath + java.io.File.separator + topic.getName();
		Set<String> externalLinks = new TreeSet<String>();
		Set<String> downloadedFiles = new TreeSet<String>();

		for (Post post : topic.getPosts()) {
			if (!postMatcher.matches(post)) {
				continue;
			}

			List<String> filesInArchives = new ArrayList<String>();
			 if (!post.getArchives().isEmpty()) {
				logger.log(Level.FINE, "Saving " + post.getArchives().size()
				    + " archives for post from user \"" + post.getAuthor().getName()
				    + "\" in topic \"" + topic.getName() + "\"");
				StringBuilder progress = new StringBuilder();
				for (Archive archive : post.getArchives()) {
					archive.setLocalPath(path);
					try {
						boolean downloaded = archive.save();
						if (downloaded) {
							downloadedFiles.add(archive.getName());
						}

						filesInArchives.addAll(archive.deflate());
						archive.delete();
						progress.append("*");
					} catch (IOException e) {
						logger.log(Level.SEVERE, "Could not save or deflate archive \""
						    + archive.getUrl() + "\"", e);
						progress.append("x");
					}
					String stillDownloading = StringUtils.repeat("_", post.getArchives().size()
					    - progress.length());
					logger.log(Level.FINER, "    Progress: " + progress.toString()
					    + stillDownloading);
				}
			}
			if (!post.getImages().isEmpty()) {
				logger.log(Level.FINE, "Saving " + post.getImages().size()
				    + " images for post from user \"" + post.getAuthor().getName()
				    + "\" in topic \"" + topic.getName() + "\"");
				StringBuilder progress = new StringBuilder();
				for (File image : post.getImages()) {
					renamer.rename(image);
					if (!containsIgnoreCase(image.getName(), filesInArchives)) {
						image.setLocalPath(path);
						try {
							boolean downloaded = image.save();
							if (downloaded) {
								downloadedFiles.add(image.getName());
							}
							progress.append("*");
						} catch (IOException e) {
							logger.log(Level.SEVERE, "Could not save image \"" + image.getUrl()
							    + "\"", e);
							progress.append("x");
						}
					} else {
						progress.append("-");
					}
					String stillDownloading = StringUtils.repeat("_", post.getImages().size()
					    - progress.length());
					logger.log(Level.FINER, "    Progress: " + progress.toString()
					    + stillDownloading);
				}
			}

			externalLinks.addAll(post.getExternalLinks());
		}

		if (!downloadedFiles.isEmpty()) {
			saveTextFile(topic.toStringFormatted(), path, "Posts.txt");
		}

		return new TopicInfos(topic.getName(), downloadedFiles, externalLinks);
	}


	private boolean containsIgnoreCase(String s, List<String> sList) {
		for (String stringInList : sList) {
			if (StringUtils.equalsIgnoreCase(s, stringInList)) {
				return true;
			}
		}
		return false;
	}


	public String getExternalLinksString(Set<String> externalLinks) {
		return createTopicInfoList(externalLinks, null);
	}


	private static String createTopicInfoList(Set<String> infosToSave, String title) {
		if (infosToSave.isEmpty()) {
			return "";
		}

		StringBuilder builder = new StringBuilder();
		if (title != null) {
			builder.append(title + "\n");
		}
		for (String externalLink : infosToSave) {
			builder.append(externalLink).append('\n');
		}
		builder.append("\n\n");
		return builder.toString();
	}


	private static void saveTextFile(String fileContent, String forumPath, String fileName) {
		try {
			FileWriter fileWriter = new FileWriter();
			fileWriter.deleteFile(forumPath, fileName);
			fileWriter.write(fileContent.getBytes(), forumPath, fileName);
		} catch (IOException e) {
			logger.log(Level.SEVERE, "Could not write forum info file \"" + fileName + "\"", e);
		}
	}

	private static class TopicInfos {
		private String topicName;

		private Set<String> downloadedFiles;

		private Set<String> externalLinks;


		TopicInfos(String topicName, Set<String> downloadedFiles, Set<String> externalLinks) {
			this.topicName = topicName;
			this.downloadedFiles = downloadedFiles;
			this.externalLinks = externalLinks;
		}


		public Set<String> getExternalLinks() {
			return externalLinks;
		}


		public String getDownloadedFilesString() {
			return createTopicInfoList(downloadedFiles, "Downloaded files in topic \"" + topicName
			    + "\":\n");
		}

	}
}
